CGI 编程教程

1. 简介

CGI(Common Gateway Interface)是一种标准协议,用于在Web服务器和外部程序之间传递信息。通过CGI,Web服务器可以执行外部程序,并将程序的输出返回给客户端浏览器。Perl是一种非常适合CGI编程的语言,因为它具有强大的字符串处理能力和丰富的内置函数。

2. 环境设置

在开始CGI编程之前,确保你已经安装了Perl,并且Web服务器(如Apache)已经配置好支持CGI。

2.1 安装Perl

在大多数Linux发行版中,Perl已经预装。如果没有,可以使用包管理器安装:

sudo apt-get install perl

2.2 配置Web服务器

以Apache为例,确保CGI模块已启用,并在配置文件中指定CGI脚本目录:

sudo a2enmod cgi
sudo systemctl restart apache2

在Apache配置文件(如/etc/apache2/sites-available/000-default.conf)中添加:

<Directory "/var/www/cgi-bin">
    Options +ExecCGI
    AddHandler cgi-script .cgi .pl
</Directory>

3. 第一个CGI程序

创建一个简单的Perl CGI脚本,并将其放在CGI脚本目录中(如/var/www/cgi-bin/)。

#!/usr/bin/perl
use strict;
use warnings;

print "Content-type: text/html\n\n";
print "<html><body>";
print "<h1>Hello, CGI World!</h1>";
print "</body></html>";

保存为hello.cgi,并赋予执行权限:

chmod +x /var/www/cgi-bin/hello.cgi

在浏览器中访问http://yourserver/cgi-bin/hello.cgi,你应该会看到“Hello, CGI World!”的页面。

4. 基本语法和数据类型

4.1 标量

标量是Perl中最基本的数据类型,可以存储字符串、数字或引用。

my $name = "Alice";
my $age = 30;

4.2 数组

数组用于存储多个标量值。

my @fruits = ("apple", "banana", "cherry");

4.3 哈希表

哈希表用于存储键值对。

my %person = (
    name => "Alice",
    age  => 30,
);

5. 变量和上下文

Perl中的变量有不同的上下文(标量上下文和列表上下文),这会影响表达式的求值方式。

my @array = (1, 2, 3);
my $count = @array;  # 标量上下文,$count 为 3
my @copy = @array;   # 列表上下文,@copy 为 (1, 2, 3)

6. 条件语句

6.1 if 语句

my $age = 18;
if ($age >= 18) {
    print "You are an adult.\n";
}

6.2 unless 语句

my $age = 17;
unless ($age >= 18) {
    print "You are not an adult.\n";
}

6.3 elsif 和 else

my $age = 20;
if ($age < 18) {
    print "You are a minor.\n";
} elsif ($age < 65) {
    print "You are an adult.\n";
} else {
    print "You are a senior.\n";
}

7. 循环

7.1 for 循环

for (my $i = 0; $i < 5; $i++) {
    print "$i\n";
}

7.2 foreach 循环

my @fruits = ("apple", "banana", "cherry");
foreach my $fruit (@fruits) {
    print "$fruit\n";
}

7.3 while 循环

my $i = 0;
while ($i < 5) {
    print "$i\n";
    $i++;
}

7.4 until 循环

my $i = 0;
until ($i >= 5) {
    print "$i\n";
    $i++;
}

8. 循环控制

8.1 next

跳过当前迭代,进入下一次循环。

for (my $i = 0; $i < 5; $i++) {
    next if $i == 2;
    print "$i\n";
}

8.2 last

立即退出循环。

for (my $i = 0; $i < 5; $i++) {
    last if $i == 2;
    print "$i\n";
}

8.3 redo

重新开始当前迭代。

for (my $i = 0; $i < 5; $i++) {
    redo if $i == 2;
    print "$i\n";
}

9. 标量、数组、哈希表

9.1 标量

标量是Perl中最基本的数据类型,可以存储字符串、数字或引用。

my $name = "Alice";
my $age = 30;

9.2 数组

数组用于存储多个标量值。

my @fruits = ("apple", "banana", "cherry");

9.3 哈希表

哈希表用于存储键值对。

my %person = (
    name => "Alice",
    age  => 30,
);

10. 引用和复杂数据结构

10.1 标量引用

my $scalar_ref = \$name;
print $$scalar_ref;  # 输出 "Alice"

10.2 数组引用

my $array_ref = \@fruits;
print @$array_ref;  # 输出 "applebananacherry"

10.3 哈希引用

my $hash_ref = \%person;
print $hash_ref->{name};  # 输出 "Alice"

11. 子程序定义和调用

11.1 定义子程序

sub greet {
    my ($name) = @_;
    print "Hello, $name!\n";
}

11.2 调用子程序

greet("Alice");  # 输出 "Hello, Alice!"

12. 参数传递

子程序可以通过@_数组接收参数。

sub add {
    my ($a, $b) = @_;
    return $a + $b;
}

my $sum = add(3, 4);
print $sum;  # 输出 7

13. 返回值

子程序可以通过return语句返回值。

sub multiply {
    my ($a, $b) = @_;
    return $a * $b;
}

my $product = multiply(3, 4);
print $product;  # 输出 12

14. 作用域和词法变量

Perl中的变量有不同的作用域,词法变量通过my关键字声明。

{
    my $local_var = "local";
    print $local_var;  # 输出 "local"
}

print $local_var;  # 错误,$local_var 未定义

15. 文件句柄

15.1 读取文件

open my $fh, "<", "file.txt" or die "Cannot open file: $!";
while (my $line = <$fh>) {
    print $line;
}
close $fh;

15.2 写入文件

open my $fh, ">", "output.txt" or die "Cannot open file: $!";
print $fh "Hello, World!\n";
close $fh;

16. 目录操作

16.1 创建目录

use File::Path qw(make_path);
make_path("new_directory");

16.2 删除目录

use File::Path qw(remove_tree);
remove_tree("new_directory");

17. 文件测试操作符

17.1 检查文件是否存在

if (-e "file.txt") {
    print "File exists.\n";
}

17.2 检查文件是否可读

if (-r "file.txt") {
    print "File is readable.\n";
}

18. 正则表达式基础

18.1 模式匹配

my $string = "Hello, World!";
if ($string =~ /Hello/) {
    print "Match found.\n";
}

18.2 替换

$string =~ s/World/Perl/;
print $string;  # 输出 "Hello, Perl!"

18.3 转化

$string =~ tr/a-z/A-Z/;
print $string;  # 输出 "HELLO, PERL!"

19. 高级正则表达式技巧

19.1 捕获组

my $string = "Hello, World!";
if ($string =~ /(Hello), (World)/) {
    print "Greeting: $1, Subject: $2\n";
}

19.2 非捕获组

my $string = "Hello, World!";
if ($string =~ /(?:Hello), (World)/) {
    print "Subject: $1\n";
}

20. 使用模块

20.1 使用内置模块

use File::Basename;
my $filename = "path/to/file.txt";
my $basename = basename($filename);
print $basename;  # 输出 "file.txt"

20.2 创建自定义模块

创建一个名为MyModule.pm的文件:

package MyModule;

sub greet {
    my ($name) = @_;
    return "Hello, $name!";
}

1;

在主程序中使用:

use MyModule;
print MyModule::greet("Alice");  # 输出 "Hello, Alice!"

21. 命名空间

Perl中的命名空间用于组织模块和避免命名冲突。

package MyNamespace::MyModule;

sub greet {
    my ($name) = @_;
    return "Hello, $name!";
}

1;

在主程序中使用:

use MyNamespace::MyModule;
print MyNamespace::MyModule::greet("Alice");  # 输出 "Hello, Alice!"

22. CPAN 使用

CPAN(Comprehensive Perl Archive Network)是Perl模块的集中存储库。

22.1 安装模块

cpan install Some::Module

22.2 使用模块

use Some::Module;

23. 类和对象

23.1 定义类

package Person;

sub new {
    my ($class, %args) = @_;
    my $self = {
        name => $args{name},
        age  => $args{age},
    };
    bless $self, $class;
    return $self;
}

sub greet {
    my ($self) = @_;
    print "Hello, I am $self->{name} and I am $self->{age} years old.\n";
}

1;

23.2 创建对象

use Person;

my $person = Person->new(name => "Alice", age => 30);
$person->greet();  # 输出 "Hello, I am Alice and I am 30 years old."

24. 继承

24.1 定义子类

package Employee;
use base 'Person';

sub new {
    my ($class, %args) = @_;
    my $self = $class->SUPER::new(%args);
    $self->{job} = $args{job};
    return $self;
}

sub job {
    my ($self) = @_;
    print "My job is $self->{job}.\n";
}

1;

24.2 使用子类

use Employee;

my $employee = Employee->new(name => "Bob", age => 25, job => "Developer");
$employee->greet();  # 输出 "Hello, I am Bob and I am 25 years old."
$employee->job();    # 输出 "My job is Developer."

25. 多态

多态允许不同的类以相同的方式调用方法。

package Animal;

sub speak {
    die "Abstract method";
}

1;

package Dog;
use base 'Animal';

sub speak {
    print "Woof!\n";
}

1;

package Cat;
use base 'Animal';

sub speak {
    print "Meow!\n";
}

1;

使用多态:

use Dog;
use Cat;

my @animals = (Dog->new(), Cat->new());
foreach my $animal (@animals) {
    $animal->speak();
}

26. 封装

封装是将数据和操作数据的方法组合在一起,并通过公共接口访问。

package BankAccount;

sub new {
    my ($class, %args) = @_;
    my $self = {
        balance => $args{balance} || 0,
    };
    bless $self, $class;
    return $self;
}

sub deposit {
    my ($self, $amount) = @_;
    $self->{balance} += $amount;
}

sub withdraw {
    my ($self, $amount) = @_;
    $self->{balance} -= $amount;
}

sub get_balance {
    my ($self) = @_;
    return $self->{balance};
}

1;

使用封装:

use BankAccount;

my $account = BankAccount->new(balance => 100);
$account->deposit(50);
$account->withdraw(30);
print $account->get_balance();  # 输出 120

27. 标量引用、数组引用、哈希引用

27.1 标量引用

my $scalar_ref = \$name;
print $$scalar_ref;  # 输出 "Alice"

27.2 数组引用

my $array_ref = \@fruits;
print @$array_ref;  # 输出 "applebananacherry"

27.3 哈希引用

my $hash_ref = \%person;
print $hash_ref->{name};  # 输出 "Alice"

28. 子程序引用

sub add {
    my ($a, $b) = @_;
    return $a + $b;
}

my $func_ref = \&add;
print $func_ref->(3, 4);  # 输出 7

29. 闭包

闭包是一个函数,它可以访问其词法作用域中的变量。

sub create_counter {
    my $count =